/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package byzantine;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Random;

/**
 *
 * @author rpathan
 */
public class LamportAlgorithm {

    private ArrayList<General> generals;
    private boolean attackCommand;
    private Integer numRounds;
    private Integer commanderIndex;
    private Integer numMessages;
    private Long simulationTime;
    private Integer numAttacking;
    private Integer numRetreating;
    private boolean algorithmSuccess;

    public LamportAlgorithm(ArrayList<General> generals, boolean attackCommand) {
        this.generals = generals;
        this.attackCommand = attackCommand;
        numRounds = 1;
        numMessages = 0;
        numAttacking = 0;
        numRetreating = 0;
        algorithmSuccess = false;

        for (int i = 0; i < generals.size(); i++) {
            if (generals.get(i).getType().equals(ByzantineModel.COMMANDER)
                    || generals.get(i).getType().equals(ByzantineModel.COMMANDER_REBEL)) {
                commanderIndex = i;
            }
            if (generals.get(i).getType().equals(ByzantineModel.COMMANDER_REBEL)
                    || generals.get(i).getType().equals(ByzantineModel.GENERAL_REBEL)) {
                numRounds++;

            }
        }
        if (numRounds > generals.size()) {
            numRounds--;
        }
    }

    public Integer getNumRounds() {
        return numRounds;
    }

    public Integer getCommanderIndex() {
        return commanderIndex;
    }

    public Integer getNumMessages() {
        return numMessages;
    }

    public Long getSimulationTime() {
        return simulationTime;
    }

    public Integer getNumAttacking() {
        return numAttacking;
    }

    public Integer getNumRetreating() {
        return numRetreating;
    }

    public boolean getAlgorithmOutcome() {
        return algorithmSuccess;
    }

    public void run() {

        // Time the algorithm
        simulationTime = System.nanoTime();

        // STAGE 1: MESSAGE PASSING

        // ROUND 0
        int round = 0;

        // Have the commander generate an attack or retreat message at random
        General commander = generals.get(commanderIndex);
        Random gen = new Random(System.nanoTime());

        String sentValue;
        if (attackCommand == true) {
            sentValue = ByzantineModel.ATTACK_VALUE;
        } else {
            sentValue = ByzantineModel.RETREAT_VALUE;
        }

        // Have the general send the message to every other general
        for (int i = 0; i < generals.size(); i++) {
            // The commander does not send the message to himself
            if (i == commanderIndex) {
                continue;
            }
            boolean traitorMessage = false;
            // If the commander is a traitor, randomize his sent messages
            if (commander.getType().equals(ByzantineModel.COMMANDER_REBEL)) {
                traitorMessage = true;
                int random = gen.nextInt(2);
                if (random == 1) {
                    sentValue = ByzantineModel.ATTACK_VALUE;
                } else {
                    sentValue = ByzantineModel.RETREAT_VALUE;
                }
            }
            Message message = new Message(sentValue, commander.getID().toString(), ByzantineModel.UNDETERMINED_VALUE, traitorMessage);
            commander.sendMessage(round, message, generals.get(i));
            numMessages++;
        }
        round++;

        // ROUND 1 - NUMROUNDS
        for (; round < numRounds; round++) {

            // Have each general send the messages from their received list
            // to each other general
            for (int i = 0; i < generals.size(); i++) {
                // The commander does not participate
                if (i == commanderIndex) {
                    continue;
                }
                General general = generals.get(i);
                ArrayList<Message> receivedMessages = general.getReceivedMessages(round - 1);
                for (Message message : receivedMessages) {
                    // Do not send the message if it contains a cycle
                    if (message.pathContains(general.getID().toString())) {
                        continue;
                    }
                    String value;
                    boolean traitorMessage = false;
                    if (message.isTraitorMessage()) {
                        traitorMessage = true;
                    }

                    // If the general is a traitor, send out TRAITOR_VALUE instead
                    if (general.getType().equals(ByzantineModel.GENERAL_REBEL)) {
                        traitorMessage = true;
                    }

                    String path = message.getPath() + Message.pathDelimiter + general.getID().toString();

                    // At this point, no decision values have been calculated
                    String calcValue = ByzantineModel.UNDETERMINED_VALUE;

                    // Send the messages from this general to every other general
                    for (int j = 0; j < generals.size(); j++) {
                        if (j == commanderIndex) {
                            continue;
                        }
                        if (general.getType().equals(ByzantineModel.GENERAL_REBEL)) {
                            int random = gen.nextInt(2);
                            if (random == 1) {
                                value = ByzantineModel.ATTACK_VALUE;
                            } else {
                                value = ByzantineModel.RETREAT_VALUE;
                            }
                        } else {
                            value = message.getValue();
                        }
                       
                        Message messageToSend = new Message(value, path, calcValue, traitorMessage);
                        general.sendMessage(round, messageToSend, generals.get(j));
                        numMessages++;
                    }
                }
            }
        }

        // STAGE 2: DECISION MAKING

        // Iterate through the rounds in reverse order, propagating majority
        // decisions back up the tree
        for (round--; round >= 0; round--) {

            // For each general
            for (int i = 0; i < generals.size(); i++) {
                // The commander does not participate
                if (i == commanderIndex) {
                    continue;
                }
                General general = generals.get(i);

                // Copy the current general's received messages into a new list
                // and sort
                ArrayList<Message> roundMessages = general.getReceivedMessages(round);
                ArrayList<Message> decisionMessages = new ArrayList<Message>();
                for (int j = 0; j < roundMessages.size(); j++) {
                    decisionMessages.add(new Message(roundMessages.get(j)));
                }
                Collections.sort(roundMessages, new PathComparator());
                Collections.sort(decisionMessages, new PathComparator());

                // If this is the first decision round, just use the value
                // as the decision value
                if (round == numRounds - 1) {
                    for (Message message : decisionMessages) {
                        message.setCalcValue(message.getValue());
                        general.addDecisionMessage(round, message);
                    }
                } // Otherwise, use the decisions from the previous decision round
                // to propagate majority decisions upward
                else {
                    ArrayList<Message> generalDecisions = generals.get(i).getDecisionMessages(round + 1);
                    // For each message in the previous round's decision messages
                    for (int k = 0; k < generals.get(i).getReceivedMessages(round).size(); k++) {
                        int countRetreat = 0;
                        int countAttack = 0;

                        // For each of the messages in the tree branch
                        for (int l = k * (generals.size() - (round + 1)); l < (k + 1) * (generals.size() - (round + 1)); l++) {

                            Message decision = generalDecisions.get(l);
                            if (decision.getCalcValue().equals(ByzantineModel.RETREAT_VALUE)) {
                                countRetreat++;
                            } else if (decision.getCalcValue().equals(ByzantineModel.ATTACK_VALUE)) {
                                countAttack++;
                            } else {
                                System.err.println("Error: Invalid calculated value");
                            }
                        }
                        String majority = findMajorityDecision(countRetreat, countAttack);
                        Message m = decisionMessages.get(k);
                        m.setCalcValue(majority);
                        generals.get(i).addDecisionMessage(round, m);

                    }
                }
            }
        }
        // Finish timing the algorithm
        simulationTime = System.nanoTime() - simulationTime;

        // Calculate the number of votes for each decision
        for (int i = 0; i < generals.size(); i++) {
            if (i == commanderIndex) {
                continue;
            }
            if (generals.get(i).getDecisionMessages(0).get(0).getCalcValue().equals(ByzantineModel.ATTACK_VALUE)) {
                numAttacking++;
            } else if (generals.get(i).getDecisionMessages(0).get(0).getCalcValue().equals(ByzantineModel.RETREAT_VALUE)) {
                numRetreating++;
            } else {
                System.err.println("Error: General has not reached a decision after algorithm has run");
            }
            if ((numAttacking > 0 && numRetreating > 0)) {
                algorithmSuccess = false;
            } else {
                algorithmSuccess = true;
            }
        }
    }

    private String findMajorityDecision(int countRetreat, int countAttack) {
        if (countRetreat > countAttack) {
            return ByzantineModel.RETREAT_VALUE;
        } else {
            return ByzantineModel.ATTACK_VALUE;
        }
    }

    private class PathComparator implements Comparator<Message> {

        public int compare(Message m1, Message m2) {
            String tokens1[] = m1.getPath().split(Message.pathDelimiter);
            String tokens2[] = m2.getPath().split(Message.pathDelimiter);
            for (int i = 0; i < tokens1.length; i++) {
                Integer value1 = Integer.parseInt(tokens1[i]);
                Integer value2 = Integer.parseInt(tokens2[i]);
                if (value1.compareTo(value2) == 0) {
                    continue;
                }
                return value1.compareTo(value2);
            }

            return 0;
        }
    }
}
